
//**************************************************************************
//**
//** PO_MAN.C : Heretic 2 : Raven Software, Corp.
//**
//** $Revision: 491 $
//** $Date: 2009-05-30 01:10:42 +0300 (Sat, 30 May 2009) $
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "h2stdinc.h"
#include "h2def.h"
#include "p_local.h"
#include "r_local.h"

// MACROS ------------------------------------------------------------------

#define PO_MAXPOLYSEGS		64

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

BOOLEAN PO_MovePolyobj(int num, int x, int y);
BOOLEAN PO_RotatePolyobj(int num, angle_t angle);
void PO_Init(int lump);

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static polyobj_t *GetPolyobj(int polyNum);
static int GetPolyobjMirror(int poly);
static void ThrustMobj(mobj_t *mobj, seg_t *seg, polyobj_t *po);
static void UpdateSegBBox(seg_t *seg);
static void RotatePt(int an, fixed_t *x, fixed_t *y, fixed_t startSpotX, fixed_t startSpotY);
static void UnLinkPolyobj(polyobj_t *po);
static void LinkPolyobj(polyobj_t *po);
static BOOLEAN CheckMobjBlocking(seg_t *seg, polyobj_t *po);
static void InitBlockMap(void);
static void IterFindPolySegs(int x, int y, seg_t **segList);
static void SpawnPolyobj(int idx, int tag, BOOLEAN crush);
static void TranslateToStartSpot(int tag, int originX, int originY);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

extern seg_t	*segs;

// PUBLIC DATA DEFINITIONS -------------------------------------------------

polyblock_t	**PolyBlockMap;
polyobj_t	*polyobjs; // list of all poly-objects on the level
int		po_NumPolyobjs;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static int	PolySegCount;
static fixed_t	PolyStartX;
static fixed_t	PolyStartY;

// CODE --------------------------------------------------------------------

// ===== Polyobj Event Code =====

//==========================================================================
//
// T_RotatePoly
//
//==========================================================================

void T_RotatePoly(polyevent_t *pe)
{
	int absSpeed;
	polyobj_t *poly;

	if (PO_RotatePolyobj(pe->polyobj, pe->speed))
	{
		absSpeed = abs(pe->speed);

		if (pe->dist == -1)
		{ // perpetual polyobj
			return;
		}
		pe->dist -= absSpeed;
		if (pe->dist <= 0)
		{
			poly = GetPolyobj(pe->polyobj);
			if (poly->specialdata == pe)
			{
				poly->specialdata = NULL;
			}
			SN_StopSequence((mobj_t *)&poly->startSpot);
			P_PolyobjFinished(poly->tag);
			P_RemoveThinker(&pe->thinker);
		}
		if (pe->dist < absSpeed)
		{
			pe->speed = pe->dist*(pe->speed < 0 ? -1 : 1);
		}
	}
}

//==========================================================================
//
// EV_RotatePoly
//
//==========================================================================

BOOLEAN EV_RotatePoly(line_t *line, byte *args, int direction, BOOLEAN overRide)
{
	int mirror;
	int polyNum;
	polyevent_t *pe;
	polyobj_t *poly;

	polyNum = args[0];
	if ((poly = GetPolyobj(polyNum)))
	{
		if (poly->specialdata && !overRide)
		{ // poly is already moving
			return false;
		}
	}
	else
	{
		I_Error("EV_RotatePoly:  Invalid polyobj num: %d\n", polyNum);
	}
	pe = (polyevent_t *) Z_Malloc(sizeof(polyevent_t), PU_LEVSPEC, NULL);
	P_AddThinker(&pe->thinker);
	pe->thinker.function = T_RotatePoly;
	pe->polyobj = polyNum;
	if (args[2])
	{
		if (args[2] == 255)
		{
			pe->dist = -1;
		}
		else
		{
			pe->dist = args[2]*(ANGLE_90/64); // Angle
		}
	}
	else
	{
		pe->dist = ANGLE_MAX-1;
	}
	pe->speed = (args[1]*direction*(ANGLE_90/64))>>3;
	poly->specialdata = pe;
	SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);

	while ((mirror = GetPolyobjMirror(polyNum)))
	{
		poly = GetPolyobj(mirror);
		if (poly && poly->specialdata && !overRide)
		{ // mirroring poly is already in motion
			break;
		}
		pe = (polyevent_t *) Z_Malloc(sizeof(polyevent_t), PU_LEVSPEC, NULL);
		P_AddThinker(&pe->thinker);
		pe->thinker.function = T_RotatePoly;
		poly->specialdata = pe;
		pe->polyobj = mirror;
		if (args[2])
		{
			if (args[2] == 255)
			{
				pe->dist = -1;
			}
			else
			{
				pe->dist = args[2]*(ANGLE_90/64); // Angle
			}
		}
		else
		{
			pe->dist = ANGLE_MAX-1;
		}
		if ((poly = GetPolyobj(polyNum)))
		{
			poly->specialdata = pe;
		}
		else
		{
			I_Error("EV_RotatePoly:  Invalid polyobj num: %d\n", polyNum);
		}
		direction = -direction;
		pe->speed = (args[1]*direction*(ANGLE_90/64))>>3;
		polyNum = mirror;
		SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
	}
	return true;
}

//==========================================================================
//
// T_MovePoly
//
//==========================================================================

void T_MovePoly(polyevent_t *pe)
{
	int absSpeed;
	polyobj_t *poly;

	if (PO_MovePolyobj(pe->polyobj, pe->xSpeed, pe->ySpeed))
	{
		absSpeed = abs(pe->speed);
		pe->dist -= absSpeed;
		if (pe->dist <= 0)
		{
			poly = GetPolyobj(pe->polyobj);
			if (poly->specialdata == pe)
			{
				poly->specialdata = NULL;
			}
			SN_StopSequence((mobj_t *)&poly->startSpot);
			P_PolyobjFinished(poly->tag);
			P_RemoveThinker(&pe->thinker);
		}
		if (pe->dist < absSpeed)
		{
			pe->speed = pe->dist*(pe->speed < 0 ? -1 : 1);
			pe->xSpeed = FixedMul(pe->speed, finecosine[pe->angle]);
			pe->ySpeed = FixedMul(pe->speed, finesine[pe->angle]);
		}
	}
}

//==========================================================================
//
// EV_MovePoly
//
//==========================================================================

BOOLEAN EV_MovePoly(line_t *line, byte *args, BOOLEAN timesEight, BOOLEAN overRide)
{
	int mirror;
	int polyNum;
	polyevent_t *pe;
	polyobj_t *poly;
	angle_t an;

	polyNum = args[0];
	if ((poly = GetPolyobj(polyNum)))
	{
		if (poly->specialdata && !overRide)
		{ // poly is already moving
			return false;
		}
	}
	else
	{
		I_Error("EV_MovePoly:  Invalid polyobj num: %d\n", polyNum);
	}
	pe = (polyevent_t *) Z_Malloc(sizeof(polyevent_t), PU_LEVSPEC, NULL);
	P_AddThinker(&pe->thinker);
	pe->thinker.function = T_MovePoly;
	pe->polyobj = polyNum;
	if (timesEight)
	{
		pe->dist = args[3]*8*FRACUNIT;
	}
	else
	{
		pe->dist = args[3]*FRACUNIT; // Distance
	}
	pe->speed = args[1]*(FRACUNIT/8);
	poly->specialdata = pe;

	an = args[2]*(ANGLE_90/64);

	pe->angle = an>>ANGLETOFINESHIFT;
	pe->xSpeed = FixedMul(pe->speed, finecosine[pe->angle]);
	pe->ySpeed = FixedMul(pe->speed, finesine[pe->angle]);
	SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);

	while ((mirror = GetPolyobjMirror(polyNum)))
	{
		poly = GetPolyobj(mirror);
		if (poly && poly->specialdata && !overRide)
		{ // mirroring poly is already in motion
			break;
		}
		pe = (polyevent_t *) Z_Malloc(sizeof(polyevent_t), PU_LEVSPEC, NULL);
		P_AddThinker(&pe->thinker);
		pe->thinker.function = T_MovePoly;
		pe->polyobj = mirror;
		poly->specialdata = pe;
		if (timesEight)
		{
			pe->dist = args[3]*8*FRACUNIT;
		}
		else
		{
			pe->dist = args[3]*FRACUNIT; // Distance
		}
		pe->speed = args[1]*(FRACUNIT/8);
		an = an + ANGLE_180; // reverse the angle
		pe->angle = an>>ANGLETOFINESHIFT;
		pe->xSpeed = FixedMul(pe->speed, finecosine[pe->angle]);
		pe->ySpeed = FixedMul(pe->speed, finesine[pe->angle]);
		polyNum = mirror;
		SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
	}
	return true;
}

//==========================================================================
//
// T_PolyDoor
//
//==========================================================================

void T_PolyDoor(polydoor_t *pd)
{
	int absSpeed;
	polyobj_t *poly;

	if (pd->tics)
	{
		if (!--pd->tics)
		{
			poly = GetPolyobj(pd->polyobj);
			SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
		}
		return;
	}
	switch (pd->type)
	{
	case PODOOR_SLIDE:
		if (PO_MovePolyobj(pd->polyobj, pd->xSpeed, pd->ySpeed))
		{
			absSpeed = abs(pd->speed);
			pd->dist -= absSpeed;
			if (pd->dist <= 0)
			{
				poly = GetPolyobj(pd->polyobj);
				SN_StopSequence((mobj_t *)&poly->startSpot);
				if (!pd->close)
				{
					pd->dist = pd->totalDist;
					pd->close = true;
					pd->tics = pd->waitTics;
					pd->direction = (ANGLE_MAX>>ANGLETOFINESHIFT) - pd->direction;
					pd->xSpeed = -pd->xSpeed;
					pd->ySpeed = -pd->ySpeed;
				}
				else
				{
					if (poly->specialdata == pd)
					{
						poly->specialdata = NULL;
					}
					P_PolyobjFinished(poly->tag);
					P_RemoveThinker(&pd->thinker);
				}
			}
		}
		else
		{
			poly = GetPolyobj(pd->polyobj);
			if (poly->crush || !pd->close)
			{ // continue moving if the poly is a crusher, or is opening
				return;
			}
			else
			{ // open back up
				pd->dist = pd->totalDist-pd->dist;
				pd->direction = (ANGLE_MAX>>ANGLETOFINESHIFT) - pd->direction;
				pd->xSpeed = -pd->xSpeed;
				pd->ySpeed = -pd->ySpeed;
				pd->close = false;
				SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
			}
		}
		break;

	case PODOOR_SWING:
		if (PO_RotatePolyobj(pd->polyobj, pd->speed))
		{
			absSpeed = abs(pd->speed);
			if (pd->dist == -1)
			{ // perpetual polyobj
				return;
			}
			pd->dist -= absSpeed;
			if (pd->dist <= 0)
			{
				poly = GetPolyobj(pd->polyobj);
				SN_StopSequence((mobj_t *)&poly->startSpot);
				if (!pd->close)
				{
					pd->dist = pd->totalDist;
					pd->close = true;
					pd->tics = pd->waitTics;
					pd->speed = -pd->speed;
				}
				else
				{
					if (poly->specialdata == pd)
					{
						poly->specialdata = NULL;
					}
					P_PolyobjFinished(poly->tag);
					P_RemoveThinker(&pd->thinker);
				}
			}
		}
		else
		{
			poly = GetPolyobj(pd->polyobj);
			if (poly->crush || !pd->close)
			{ // continue moving if the poly is a crusher, or is opening
				return;
			}
			else
			{ // open back up and rewait
				pd->dist = pd->totalDist-pd->dist;
				pd->speed = -pd->speed;
				pd->close = false;
				SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
			}
		}
		break;

	default:
		break;
	}
}

//==========================================================================
//
// EV_OpenPolyDoor
//
//==========================================================================

BOOLEAN EV_OpenPolyDoor(line_t *line, byte *args, podoortype_t type)
{
	int mirror;
	int polyNum;
	polydoor_t *pd;
	polyobj_t *poly;
	angle_t an = 0;

	polyNum = args[0];
	if ((poly = GetPolyobj(polyNum)))
	{
		if (poly->specialdata)
		{ // poly is already moving
			return false;
		}
	}
	else
	{
		I_Error("EV_OpenPolyDoor:  Invalid polyobj num: %d\n", polyNum);
	}
	pd = (polydoor_t *) Z_Malloc(sizeof(polydoor_t), PU_LEVSPEC, NULL);
	memset(pd, 0, sizeof(polydoor_t));
	P_AddThinker(&pd->thinker);
	pd->thinker.function = T_PolyDoor;
	pd->type = type;
	pd->polyobj = polyNum;
	if (type == PODOOR_SLIDE)
	{
		pd->waitTics = args[4];
		pd->speed = args[1]*(FRACUNIT/8);
		pd->totalDist = args[3]*FRACUNIT; // Distance
		pd->dist = pd->totalDist;
		an = args[2]*(ANGLE_90/64);
		pd->direction = an>>ANGLETOFINESHIFT;
		pd->xSpeed = FixedMul(pd->speed, finecosine[pd->direction]);
		pd->ySpeed = FixedMul(pd->speed, finesine[pd->direction]);
		SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
	}
	else if (type == PODOOR_SWING)
	{
		pd->waitTics = args[3];
		pd->direction = 1; // ADD:  PODOOR_SWINGL, PODOOR_SWINGR
		pd->speed = (args[1]*pd->direction*(ANGLE_90/64))>>3;
		pd->totalDist = args[2]*(ANGLE_90/64);
		pd->dist = pd->totalDist;
		SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
	}

	poly->specialdata = pd;

	while ((mirror = GetPolyobjMirror(polyNum)))
	{
		poly = GetPolyobj(mirror);
		if (poly && poly->specialdata)
		{ // mirroring poly is already in motion
			break;
		}
		pd = (polydoor_t *) Z_Malloc(sizeof(polydoor_t), PU_LEVSPEC, NULL);
		memset(pd, 0, sizeof(polydoor_t));
		P_AddThinker(&pd->thinker);
		pd->thinker.function = T_PolyDoor;
		pd->polyobj = mirror;
		pd->type = type;
		poly->specialdata = pd;
		if (type == PODOOR_SLIDE)
		{
			pd->waitTics = args[4];
			pd->speed = args[1]*(FRACUNIT/8);
			pd->totalDist = args[3]*FRACUNIT; // Distance
			pd->dist = pd->totalDist;
			an = an+ANGLE_180; // reverse the angle
			pd->direction = an>>ANGLETOFINESHIFT;
			pd->xSpeed = FixedMul(pd->speed, finecosine[pd->direction]);
			pd->ySpeed = FixedMul(pd->speed, finesine[pd->direction]);
			SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
		}
		else if (type == PODOOR_SWING)
		{
			pd->waitTics = args[3];
			pd->direction = -1; // ADD:  same as above
			pd->speed = (args[1]*pd->direction*(ANGLE_90/64))>>3;
			pd->totalDist = args[2]*(ANGLE_90/64);
			pd->dist = pd->totalDist;
			SN_StartSequence((mobj_t *)&poly->startSpot, SEQ_DOOR_STONE + poly->seqType);
		}
		polyNum = mirror;
	}
	return true;
}

// ===== Higher Level Poly Interface code =====

//==========================================================================
//
// GetPolyobj
//
//==========================================================================

static polyobj_t *GetPolyobj(int polyNum)
{
	int i;

	for (i = 0; i < po_NumPolyobjs; i++)
	{
		if (polyobjs[i].tag == polyNum)
		{
			return &polyobjs[i];
		}
	}
	return NULL;
}

//==========================================================================
//
// GetPolyobjMirror
//
//==========================================================================

static int GetPolyobjMirror(int poly)
{
	int i;

	for (i = 0; i < po_NumPolyobjs; i++)
	{
		if (polyobjs[i].tag == poly)
		{
			return ((*polyobjs[i].segs)->linedef->arg2);
		}
	}
	return 0;
}

//==========================================================================
//
// ThrustMobj
//
//==========================================================================

static void ThrustMobj(mobj_t *mobj, seg_t *seg, polyobj_t *po)
{
	int thrustAngle;
	int thrustX;
	int thrustY;
	polyevent_t *pe;
	int force;

	if (!(mobj->flags&MF_SHOOTABLE) && !mobj->player)
	{
		return;
	}
	thrustAngle = (seg->angle - ANGLE_90)>>ANGLETOFINESHIFT;

	pe = (polyevent_t *) po->specialdata;
	if (pe)
	{
		if (pe->thinker.function == T_RotatePoly)
		{
			force = pe->speed>>8;
		}
		else
		{
			force = pe->speed>>3;
		}
		if (force < FRACUNIT)
		{
			force = FRACUNIT;
		}
		else if (force > 4*FRACUNIT)
		{
			force = 4*FRACUNIT;
		}
	}
	else
	{
		force = FRACUNIT;
	}

	thrustX = FixedMul(force, finecosine[thrustAngle]);
	thrustY = FixedMul(force, finesine[thrustAngle]);
	mobj->momx += thrustX;
	mobj->momy += thrustY;
	if (po->crush)
	{
		if (!P_CheckPosition(mobj, mobj->x + thrustX, mobj->y + thrustY))
		{
			P_DamageMobj(mobj, NULL, NULL, 3);
		}
	}
}

//==========================================================================
//
// UpdateSegBBox
//
//==========================================================================

static void UpdateSegBBox(seg_t *seg)
{
	line_t *line;

	line = seg->linedef;

	if (seg->v1->x < seg->v2->x)
	{
		line->bbox[BOXLEFT] = seg->v1->x;
		line->bbox[BOXRIGHT] = seg->v2->x;
	}
	else
	{
		line->bbox[BOXLEFT] = seg->v2->x;
		line->bbox[BOXRIGHT] = seg->v1->x;
	}
	if (seg->v1->y < seg->v2->y)
	{
		line->bbox[BOXBOTTOM] = seg->v1->y;
		line->bbox[BOXTOP] = seg->v2->y;
	}
	else
	{
		line->bbox[BOXBOTTOM] = seg->v2->y;
		line->bbox[BOXTOP] = seg->v1->y;
	}

	// Update the line's slopetype
	line->dx = line->v2->x - line->v1->x;
	line->dy = line->v2->y - line->v1->y;
	if (!line->dx)
	{
		line->slopetype = ST_VERTICAL;
	}
	else if (!line->dy)
	{
		line->slopetype = ST_HORIZONTAL;
	}
	else
	{
		if (FixedDiv(line->dy, line->dx) > 0)
		{
			line->slopetype = ST_POSITIVE;
		}
		else
		{
			line->slopetype = ST_NEGATIVE;
		}
	}
}

//==========================================================================
//
// PO_MovePolyobj
//
//==========================================================================

BOOLEAN PO_MovePolyobj(int num, int x, int y)
{
	int count;
	seg_t **segList;
	seg_t **veryTempSeg;
	polyobj_t *po;
	vertex_t *prevPts;
	BOOLEAN blocked;

	if (!(po = GetPolyobj(num)))
	{
		I_Error("PO_MovePolyobj:  Invalid polyobj number: %d\n", num);
	}

	UnLinkPolyobj(po);

	segList = po->segs;
	prevPts = po->prevPts;
	blocked = false;

	validcount++;
	for (count = po->numsegs; count; count--, segList++, prevPts++)
	{
		if ((*segList)->linedef->validcount != validcount)
		{
			(*segList)->linedef->bbox[BOXTOP] += y;
			(*segList)->linedef->bbox[BOXBOTTOM] += y;
			(*segList)->linedef->bbox[BOXLEFT] += x;
			(*segList)->linedef->bbox[BOXRIGHT] += x;
			(*segList)->linedef->validcount = validcount;
		}
		for (veryTempSeg = po->segs; veryTempSeg != segList; veryTempSeg++)
		{
			if ((*veryTempSeg)->v1 == (*segList)->v1)
			{
				break;
			}
		}
		if (veryTempSeg == segList)
		{
			(*segList)->v1->x += x;
			(*segList)->v1->y += y;
		}
		(*prevPts).x += x;	// previous points are unique for each seg
		(*prevPts).y += y;
	}
	segList = po->segs;
	for (count = po->numsegs; count; count--, segList++)
	{
		if (CheckMobjBlocking(*segList, po))
		{
			blocked = true;
		}
	}
	if (blocked)
	{
		count = po->numsegs;
		segList = po->segs;
		prevPts = po->prevPts;
		validcount++;
		while (count--)
		{
			if ((*segList)->linedef->validcount != validcount)
			{
				(*segList)->linedef->bbox[BOXTOP] -= y;
				(*segList)->linedef->bbox[BOXBOTTOM] -= y;
				(*segList)->linedef->bbox[BOXLEFT] -= x;
				(*segList)->linedef->bbox[BOXRIGHT] -= x;
				(*segList)->linedef->validcount = validcount;
			}
			for (veryTempSeg = po->segs; veryTempSeg != segList; veryTempSeg++)
			{
				if ((*veryTempSeg)->v1 == (*segList)->v1)
				{
					break;
				}
			}
			if (veryTempSeg == segList)
			{
				(*segList)->v1->x -= x;
				(*segList)->v1->y -= y;
			}
			(*prevPts).x -= x;
			(*prevPts).y -= y;
			segList++;
			prevPts++;
		}
		LinkPolyobj(po);
		return false;
	}
	po->startSpot.x += x;
	po->startSpot.y += y;
	LinkPolyobj(po);
	return true;
}

//==========================================================================
//
// RotatePt
//
//==========================================================================

static void RotatePt(int an, fixed_t *x, fixed_t *y, fixed_t startSpotX, fixed_t startSpotY)
{
	fixed_t xtr, ytr;
	fixed_t gxt, gyt;

	xtr = *x;
	ytr = *y;

	gxt = FixedMul(xtr, finecosine[an]);
	gyt = FixedMul(ytr, finesine[an]);
	*x = (gxt - gyt) + startSpotX;

	gxt = FixedMul(xtr, finesine[an]);
	gyt = FixedMul(ytr, finecosine[an]);
	*y = (gyt + gxt) + startSpotY;
}

//==========================================================================
//
// PO_RotatePolyobj
//
//==========================================================================

BOOLEAN PO_RotatePolyobj(int num, angle_t angle)
{
	int count;
	seg_t **segList;
	vertex_t *originalPts;
	vertex_t *prevPts;
	int an;
	polyobj_t *po;
	BOOLEAN blocked;

	if (!(po = GetPolyobj(num)))
	{
		I_Error("PO_RotatePolyobj:  Invalid polyobj number: %d\n", num);
	}
	an = (po->angle + angle)>>ANGLETOFINESHIFT;

	UnLinkPolyobj(po);

	segList = po->segs;
	originalPts = po->originalPts;
	prevPts = po->prevPts;

	for (count = po->numsegs; count; count--, segList++, originalPts++, prevPts++)
	{
		prevPts->x = (*segList)->v1->x;
		prevPts->y = (*segList)->v1->y;
		(*segList)->v1->x = originalPts->x;
		(*segList)->v1->y = originalPts->y;
		RotatePt(an, &(*segList)->v1->x, &(*segList)->v1->y, po->startSpot.x, po->startSpot.y);
	}
	segList = po->segs;
	blocked = false;
	validcount++;
	for (count = po->numsegs; count; count--, segList++)
	{
		if (CheckMobjBlocking(*segList, po))
		{
			blocked = true;
		}
		if ((*segList)->linedef->validcount != validcount)
		{
			UpdateSegBBox(*segList);
			(*segList)->linedef->validcount = validcount;
		}
		(*segList)->angle += angle;
	}
	if (blocked)
	{
		segList = po->segs;
		prevPts = po->prevPts;
		for (count = po->numsegs; count; count--, segList++, prevPts++)
		{
			(*segList)->v1->x = prevPts->x;
			(*segList)->v1->y = prevPts->y;
		}
		segList = po->segs;
		validcount++;
		for (count = po->numsegs; count; count--, segList++, prevPts++)
		{
			if ((*segList)->linedef->validcount != validcount)
			{
				UpdateSegBBox(*segList);
				(*segList)->linedef->validcount = validcount;
			}
			(*segList)->angle -= angle;
		}
		LinkPolyobj(po);
		return false;
	}
	po->angle += angle;
	LinkPolyobj(po);
	return true;
}

//==========================================================================
//
// UnLinkPolyobj
//
//==========================================================================

static void UnLinkPolyobj(polyobj_t *po)
{
	polyblock_t *link;
	int i, j;
	int idx;

	// remove the polyobj from each blockmap section
	for (j = po->bbox[BOXBOTTOM]; j <= po->bbox[BOXTOP]; j++)
	{
		idx = j * bmapwidth;
		for (i = po->bbox[BOXLEFT]; i <= po->bbox[BOXRIGHT]; i++)
		{
			if (i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight)
			{
				link = PolyBlockMap[idx + i];
				while (link != NULL && link->polyobj != po)
				{
					link = link->next;
				}
				if (link == NULL)
				{ // polyobj not located in the link cell
					continue;
				}
				link->polyobj = NULL;
			}
		}
	}
}

//==========================================================================
//
// LinkPolyobj
//
//==========================================================================

static void LinkPolyobj(polyobj_t *po)
{
	int leftX, rightX;
	int topY, bottomY;
	seg_t **tempSeg;
	polyblock_t **link;
	polyblock_t *tempLink;
	int i, j;

	// calculate the polyobj bbox
	tempSeg = po->segs;
	rightX = leftX = (*tempSeg)->v1->x;
	topY = bottomY = (*tempSeg)->v1->y;

	for (i = 0; i < po->numsegs; i++, tempSeg++)
	{
		if ((*tempSeg)->v1->x > rightX)
		{
			rightX = (*tempSeg)->v1->x;
		}
		if ((*tempSeg)->v1->x < leftX)
		{
			leftX = (*tempSeg)->v1->x;
		}
		if ((*tempSeg)->v1->y > topY)
		{
			topY = (*tempSeg)->v1->y;
		}
		if ((*tempSeg)->v1->y < bottomY)
		{
			bottomY = (*tempSeg)->v1->y;
		}
	}
	po->bbox[BOXRIGHT] = (rightX - bmaporgx)>>MAPBLOCKSHIFT;
	po->bbox[BOXLEFT] = (leftX - bmaporgx)>>MAPBLOCKSHIFT;
	po->bbox[BOXTOP] = (topY - bmaporgy)>>MAPBLOCKSHIFT;
	po->bbox[BOXBOTTOM] = (bottomY - bmaporgy)>>MAPBLOCKSHIFT;
	// add the polyobj to each blockmap section
	for (j = po->bbox[BOXBOTTOM]*bmapwidth; j <= po->bbox[BOXTOP]*bmapwidth;
								j += bmapwidth)
	{
		for (i = po->bbox[BOXLEFT]; i <= po->bbox[BOXRIGHT]; i++)
		{
			if (i >= 0 && i < bmapwidth && j >= 0 && j < bmapheight*bmapwidth)
			{
				link = &PolyBlockMap[j + i];
				if (!(*link))
				{ // Create a new link at the current block cell
					*link = (polyblock_t *) Z_Malloc(sizeof(polyblock_t), PU_LEVEL, NULL);
					(*link)->next = NULL;
					(*link)->prev = NULL;
					(*link)->polyobj = po;
					continue;
				}
				else
				{
					tempLink = *link;
					while (tempLink->next != NULL && tempLink->polyobj != NULL)
					{
						tempLink = tempLink->next;
					}
				}
				if (tempLink->polyobj == NULL)
				{
					tempLink->polyobj = po;
					continue;
				}
				else
				{
					tempLink->next = (polyblock_t *) Z_Malloc(sizeof(polyblock_t), PU_LEVEL, NULL);
					tempLink->next->next = NULL;
					tempLink->next->prev = tempLink;
					tempLink->next->polyobj = po;
				}
			}
			// else, don't link the polyobj, since it's off the map
		}
	}
}

//==========================================================================
//
// CheckMobjBlocking
//
//==========================================================================

static BOOLEAN CheckMobjBlocking(seg_t *seg, polyobj_t *po)
{
	mobj_t *mobj;
	int i, j;
	int left, right, top, bottom;
	int tmbbox[4];
	line_t *ld;
	BOOLEAN blocked;

	ld = seg->linedef;

	top = (ld->bbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
	bottom = (ld->bbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
	left = (ld->bbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
	right = (ld->bbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;

	blocked = false;

	bottom = bottom < 0 ? 0 : bottom;
	bottom = bottom >= bmapheight ? bmapheight - 1 : bottom;
	top = top < 0 ? 0 : top;
	top = top >= bmapheight  ? bmapheight - 1 : top;
	left = left < 0 ? 0 : left;
	left = left >= bmapwidth ? bmapwidth - 1 : left;
	right = right < 0 ? 0 : right;
	right = right >= bmapwidth ?  bmapwidth - 1 : right;

	for (j = bottom*bmapwidth; j <= top*bmapwidth; j += bmapwidth)
	{
		for (i = left; i <= right; i++)
		{
			for (mobj = blocklinks[j+i]; mobj; mobj = mobj->bnext)
			{
				if (mobj->flags&MF_SOLID || mobj->player)
				{
					tmbbox[BOXTOP] = mobj->y + mobj->radius;
					tmbbox[BOXBOTTOM] = mobj->y - mobj->radius;
					tmbbox[BOXLEFT] = mobj->x - mobj->radius;
					tmbbox[BOXRIGHT] = mobj->x + mobj->radius;

					if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT]
						|| tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
						|| tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM]
						|| tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
					{
						continue;
					}
					if (P_BoxOnLineSide(tmbbox, ld) != -1)
					{
						continue;
					}
					ThrustMobj(mobj, seg, po);
					blocked = true;
				}
			}
		}
	}
	return blocked;
}

//==========================================================================
//
// InitBlockMap
//
//==========================================================================

static void InitBlockMap(void)
{
	int i;
	int j;
	seg_t **segList;
	int area;
	int leftX, rightX;
	int topY, bottomY;

	PolyBlockMap = (polyblock_t **) Z_Malloc(bmapwidth*bmapheight*sizeof(polyblock_t *), PU_LEVEL, NULL);
	memset(PolyBlockMap, 0, bmapwidth*bmapheight*sizeof(polyblock_t *));

	for (i = 0; i < po_NumPolyobjs; i++)
	{
		LinkPolyobj(&polyobjs[i]);

		// calculate a rough area
		// right now, working like shit...gotta fix this...
		segList = polyobjs[i].segs;
		leftX = rightX = (*segList)->v1->x;
		topY = bottomY = (*segList)->v1->y;
		for (j = 0; j < polyobjs[i].numsegs; j++, segList++)
		{
			if ((*segList)->v1->x < leftX)
			{
				leftX = (*segList)->v1->x;
			}
			if ((*segList)->v1->x > rightX)
			{
				rightX = (*segList)->v1->x;
			}
			if ((*segList)->v1->y < bottomY)
			{
				bottomY = (*segList)->v1->y;
			}
			if ((*segList)->v1->y > topY)
			{
				topY = (*segList)->v1->y;
			}
		}
		area = ((rightX>>FRACBITS) - (leftX>>FRACBITS)) * ((topY>>FRACBITS) - (bottomY>>FRACBITS));

//		fprintf(stdaux, "Area of Polyobj[%d]: %d\n", polyobjs[i].tag, area);
//		fprintf(stdaux, "\t[%d]\n[%d]\t\t[%d]\n\t[%d]\n",
//				 topY>>FRACBITS, leftX>>FRACBITS,
//				 rightX>>FRACBITS, bottomY>>FRACBITS);
	}
}

//==========================================================================
//
// IterFindPolySegs
//
// Passing NULL for segList will cause IterFindPolySegs to
// count the number of segs in the polyobj
//==========================================================================

static void IterFindPolySegs(int x, int y, seg_t **segList)
{
	int i;

	if (x == PolyStartX && y == PolyStartY)
	{
		return;
	}
	for (i = 0; i < numsegs; i++)
	{
		if (segs[i].v1->x == x && segs[i].v1->y == y)
		{
			if (!segList)
			{
				PolySegCount++;
			}
			else
			{
				*segList++ = &segs[i];
			}
			IterFindPolySegs(segs[i].v2->x, segs[i].v2->y, segList);
			return;
		}
	}
	I_Error("IterFindPolySegs:  Non-closed Polyobj located.\n");
}


//==========================================================================
//
// SpawnPolyobj
//
//==========================================================================

static void SpawnPolyobj(int idx, int tag, BOOLEAN crush)
{
	int i;
	int j;
	int psIndex;
	int psIndexOld;
	seg_t *polySegList[PO_MAXPOLYSEGS];

	for (i = 0; i < numsegs; i++)
	{
		if (segs[i].linedef->special == PO_LINE_START &&
			segs[i].linedef->arg1 == tag)
		{
			if (polyobjs[idx].segs)
			{
				I_Error("SpawnPolyobj:  Polyobj %d already spawned.\n", tag);
			}
			segs[i].linedef->special = 0;
			segs[i].linedef->arg1 = 0;
			PolySegCount = 1;
			PolyStartX = segs[i].v1->x;
			PolyStartY = segs[i].v1->y;
			IterFindPolySegs(segs[i].v2->x, segs[i].v2->y, NULL);

			polyobjs[idx].numsegs = PolySegCount;
			polyobjs[idx].segs = (seg_t **) Z_Malloc(PolySegCount*sizeof(seg_t *), PU_LEVEL, NULL);
			*(polyobjs[idx].segs) = &segs[i]; // insert the first seg
			IterFindPolySegs(segs[i].v2->x, segs[i].v2->y, polyobjs[idx].segs + 1);
			polyobjs[idx].crush = crush;
			polyobjs[idx].tag = tag;
			polyobjs[idx].seqType = segs[i].linedef->arg3;
			if (polyobjs[idx].seqType < 0 
				|| polyobjs[idx].seqType >= SEQTYPE_NUMSEQ)
			{
				polyobjs[idx].seqType = 0;
			}
			break;
		}
	}
	if (!polyobjs[idx].segs)
	{ // didn't find a polyobj through PO_LINE_START
		psIndex = 0;
		polyobjs[idx].numsegs = 0;
		for (j = 1; j < PO_MAXPOLYSEGS; j++)
		{
			psIndexOld = psIndex;
			for (i = 0; i < numsegs; i++)
			{
				if (segs[i].linedef->special == PO_LINE_EXPLICIT &&
					segs[i].linedef->arg1 == tag)
				{
					if (!segs[i].linedef->arg2)
					{
						I_Error("SpawnPolyobj:  Explicit line missing order number (probably %d) in poly %d.\n",
							j + 1, tag);
					}
					if (segs[i].linedef->arg2 == j)
					{
						polySegList[psIndex] = &segs[i];
						polyobjs[idx].numsegs++;
						psIndex++;
						if (psIndex > PO_MAXPOLYSEGS)
						{
							I_Error("SpawnPolyobj:  psIndex > PO_MAXPOLYSEGS\n");
						}
					}
				}
			}
			// Clear out any specials for these segs...we cannot clear them out
			//	in the above loop, since we aren't guaranteed one seg per
			//		linedef.
			for (i = 0; i < numsegs; i++)
			{
				if (segs[i].linedef->special == PO_LINE_EXPLICIT &&
					segs[i].linedef->arg1 == tag && segs[i].linedef->arg2 == j)
				{
					segs[i].linedef->special = 0;
					segs[i].linedef->arg1 = 0;
				}
			}
			if (psIndex == psIndexOld)
			{ // Check if an explicit line order has been skipped
				// A line has been skipped if there are any more explicit
				// lines with the current tag value
				for (i = 0; i < numsegs; i++)
				{
					if (segs[i].linedef->special == PO_LINE_EXPLICIT &&
						segs[i].linedef->arg1 == tag)
					{
						I_Error("SpawnPolyobj:  Missing explicit line %d for poly %d\n",
							j, tag);
					}
				}
			}
		}
		if (polyobjs[idx].numsegs)
		{
			PolySegCount = polyobjs[idx].numsegs; // PolySegCount used globally
			polyobjs[idx].crush = crush;
			polyobjs[idx].tag = tag;
			polyobjs[idx].segs = (seg_t **) Z_Malloc(polyobjs[idx].numsegs*sizeof(seg_t *), PU_LEVEL, NULL);
			for (i = 0; i < polyobjs[idx].numsegs; i++)
			{
				polyobjs[idx].segs[i] = polySegList[i];
			}
			polyobjs[idx].seqType = (*polyobjs[idx].segs)->linedef->arg4;
		}
		// Next, change the polyobjs first line to point to a mirror
		//		if it exists
		(*polyobjs[idx].segs)->linedef->arg2 = (*polyobjs[idx].segs)->linedef->arg3;
	}
}

//==========================================================================
//
// TranslateToStartSpot
//
//==========================================================================

static void TranslateToStartSpot(int tag, int originX, int originY)
{
	seg_t **tempSeg;
	seg_t **veryTempSeg;
	vertex_t *tempPt;
	subsector_t *sub;
	polyobj_t *po;
	int deltaX;
	int deltaY;
	vertex_t avg; // used to find a polyobj's center, and hence subsector
	int i;

	po = NULL;
	for (i = 0; i < po_NumPolyobjs; i++)
	{
		if (polyobjs[i].tag == tag)
		{
			po = &polyobjs[i];
			break;
		}
	}
	if (!po)
	{ // didn't match the tag with a polyobj tag
		I_Error("TranslateToStartSpot:  Unable to match polyobj tag: %d\n", tag);
	}
	if (po->segs == NULL)
	{
		I_Error("TranslateToStartSpot:  Anchor point located without a StartSpot point: %d\n", tag);
	}
	po->originalPts = (vertex_t *) Z_Malloc(po->numsegs*sizeof(vertex_t), PU_LEVEL, NULL);
	po->prevPts = (vertex_t *) Z_Malloc(po->numsegs*sizeof(vertex_t), PU_LEVEL, NULL);
	deltaX = originX-po->startSpot.x;
	deltaY = originY-po->startSpot.y;

	tempSeg = po->segs;
	tempPt = po->originalPts;
	avg.x = 0;
	avg.y = 0;

	validcount++;
	for (i = 0; i < po->numsegs; i++, tempSeg++, tempPt++)
	{
		if ((*tempSeg)->linedef->validcount != validcount)
		{
			(*tempSeg)->linedef->bbox[BOXTOP] -= deltaY;
			(*tempSeg)->linedef->bbox[BOXBOTTOM] -= deltaY;
			(*tempSeg)->linedef->bbox[BOXLEFT] -= deltaX;
			(*tempSeg)->linedef->bbox[BOXRIGHT] -= deltaX;
			(*tempSeg)->linedef->validcount = validcount;
		}
		for (veryTempSeg = po->segs; veryTempSeg != tempSeg; veryTempSeg++)
		{
			if ((*veryTempSeg)->v1 == (*tempSeg)->v1)
			{
				break;
			}
		}
		if (veryTempSeg == tempSeg)
		{ // the point hasn't been translated, yet
			(*tempSeg)->v1->x -= deltaX;
			(*tempSeg)->v1->y -= deltaY;
		}
		avg.x += (*tempSeg)->v1->x>>FRACBITS;
		avg.y += (*tempSeg)->v1->y>>FRACBITS;
		// the original Pts are based off the startSpot Pt, and are
		// unique to each seg, not each linedef
		tempPt->x = (*tempSeg)->v1->x - po->startSpot.x;
		tempPt->y = (*tempSeg)->v1->y - po->startSpot.y;
	}
	avg.x /= po->numsegs;
	avg.y /= po->numsegs;
	sub = R_PointInSubsector(avg.x<<FRACBITS, avg.y<<FRACBITS);
	if (sub->poly != NULL)
	{
		I_Error("PO_TranslateToStartSpot:  Multiple polyobjs in a single subsector.\n");
	}
	sub->poly = po;
}

//==========================================================================
//
// PO_Init
//
//==========================================================================

void PO_Init(int lump)
{
	void		*data;
	int		i;
	mapthing_t	*mt;
	int		numthings;
	int		polyIndex;

	polyobjs = (polyobj_t *) Z_Malloc(po_NumPolyobjs*sizeof(polyobj_t), PU_LEVEL, NULL);
	memset(polyobjs, 0, po_NumPolyobjs*sizeof(polyobj_t));

	data = W_CacheLumpNum(lump, PU_STATIC);
	numthings = W_LumpLength(lump)/sizeof(mapthing_t);
	mt = (mapthing_t *)data;
	polyIndex = 0; // index polyobj number
	// Find the startSpot points, and spawn each polyobj
	for (i = 0; i < numthings; i++, mt++)
	{
		mt->x = SHORT(mt->x);
		mt->y = SHORT(mt->y);
		mt->angle = SHORT(mt->angle);
		mt->type = SHORT(mt->type);

		// 3001 = no crush, 3002 = crushing
		if (mt->type == PO_SPAWN_TYPE || mt->type == PO_SPAWNCRUSH_TYPE)
		{ // Polyobj StartSpot Pt.
			polyobjs[polyIndex].startSpot.x = mt->x<<FRACBITS;
			polyobjs[polyIndex].startSpot.y = mt->y<<FRACBITS;
			SpawnPolyobj(polyIndex, mt->angle, (mt->type == PO_SPAWNCRUSH_TYPE));
			polyIndex++;
		}
	}
	mt = (mapthing_t *)data;
	for (i = 0; i < numthings; i++, mt++)
	{
		/* NOTE: we byte swapped the fields in
		the above loop, don't swap here again */
		if (mt->type == PO_ANCHOR_TYPE)
		{ // Polyobj Anchor Pt.
			TranslateToStartSpot(mt->angle, mt->x<<FRACBITS, mt->y<<FRACBITS);
		}
	}
	Z_Free (data);
	// check for a startspot without an anchor point
	for (i = 0; i < po_NumPolyobjs; i++)
	{
		if (!polyobjs[i].originalPts)
		{
			I_Error("PO_Init:  StartSpot located without an Anchor point: %d\n",
				polyobjs[i].tag);
		}
	}
	InitBlockMap();
}

//==========================================================================
//
// PO_Busy
//
//==========================================================================

BOOLEAN PO_Busy(int polyobj)
{
	polyobj_t *poly;

	poly = GetPolyobj(polyobj);
	if (!poly->specialdata)
	{
		return false;
	}
	else
	{
		return true;
	}
}

